/*
 [The "BSD license"]
 Copyright (c) 2011-2011 Joel Li (李家智)
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
 3. The name of the author may not be used to endorse or promote products
     derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.bee.tl.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.antlr.runtime.ANTLRReaderStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.bee.tl.core.io.ByteWriter;

public class BeeTemplate extends AbstractTemplate
{

	ExpRuntime expr = new ExpRuntime();;
	String root = null;
	String file = null;
	boolean sourceisFile = false;
	String input = null;

	// 0 reset 1 continue 2 break;
	int jump = 0;
	static final int FOR_RESET = 0;
	static final int FOR_CONTINUE = 1;
	static final int FOR_BREAK = 2;
	static final int RETURN = 4;

	// antlr parser
	ANTLRReaderStream antlrReader = null;
	BeeLexer lexer = null;
	CommonTokenStream tokens = null;
	BeeParser parser = null;
	String displayNull = "null";
	RecognitionException lasetRe = null;

	public BeeTemplate(String input)
	{
		this.reader = new StringReader(input);
		this.input = input;

		resource = new Resource(input);

	}

	public BeeTemplate(File file) throws IOException
	{
		this(new InputStreamReader(new FileInputStream(file)));
		resource = new Resource(file, "UTF-8");

	}

	public BeeTemplate(File file, String charset) throws IOException
	{
		this(new InputStreamReader(new FileInputStream(file), charset));
		resource = new Resource(file, charset);

	}

	public BeeTemplate(String input, Context ctx)
	{
		this.reader = new StringReader(input);
		this.input = input;
		this.context = ctx;
		resource = new Resource(input);

	}

	public BeeTemplate(File file, Context ctx) throws IOException
	{
		this(new FileReader(file));
		this.context = ctx;
		resource = new Resource(file, group.charset);
	}

	public BeeTemplate(Reader reader) throws IOException
	{
		this.reader = reader;
		// do not set Resource since it's must keep in memory
		// resource = new Resource(file);

	}

	// just for copy
	BeeTemplate()
	{

	}

	public String getContent(int start, int end) throws IOException
	{
		return resource.getLines(start, end);
	}

	public void config(String statementStart, String statementEnd, String holderStart, String holderEnd)
	{
		this.setStatementStart(statementStart);
		this.setStatementEnd(statementEnd);
		this.setPlaceholderStart(holderStart);
		this.setPlaceholderEnd(holderEnd);
	}

	public void config(String statementStart, String statementEnd, String holderStart, String holderEnd,
			boolean nativeCall)
	{
		this.setStatementStart(statementStart);
		this.setStatementEnd(statementEnd);
		this.setPlaceholderStart(holderStart);
		this.setPlaceholderEnd(holderEnd);
		if (nativeCall)
			this.enableNativeCall();
	}

	protected void parse() throws IOException
	{

		if (!isParsed)
		{
			isParsed = true;

			if (this.group == null)
			{
				this.group = new GroupTemplate();
			}
			else
			{
				copyGroupPropertie();
			}

			tf = new Transformator(placeholderStart, placeholderEnd, this.statementStart, this.statementEnd);

			if (resource == null)
			{
				// 是字符流，不得不记在内存中
				resource = new Resource(tf.cs);
			}

			Reader newReader = tf.transform(this.reader);
			resource.setCR(tf.lineSeparator);
			if (directByteOutput)
			{
				// 直接二进制输出
				Map<String, String> texts = tf.getTextMap();
				Map<String, byte[]> byteMap = new HashMap<String, byte[]>(texts.size());
				for (Map.Entry<String, String> entry : texts.entrySet())
				{
					byteMap.put(entry.getKey(), entry.getValue().getBytes(group.getCharset()));
				}
				this.context.putTextVar(byteMap);
			}
			else
			{
				this.context.putTextVar(tf.getTextMap());
			}

			antlrReader = new ANTLRReaderStream(newReader);
			lexer = new BeeLexer(antlrReader);
			tokens = new CommonTokenStream(lexer);
			parser = new BeeParser(tokens);
			parser.setTemplate(this);
			parser.setStrictMVC(this.isStrict);
			parser.setNativeCall(this.nativeCall);

			parser.setTreeAdaptor(new BeeNodeAdaptor());

			BeeParser.prog_return r = null;
			try
			{
				r = parser.prog();
				this.isParseSuccess = true;
			}
			catch (MVCStrictException ex)
			{
				this.isParseSuccess = false;
				this.lasetRe = ex;
				return;
			}
			catch (WrapperAntlrLexerException e)
			{
				this.lasetRe = (RecognitionException) e.getCause();
				this.isParseSuccess = false;
				return;
			}
			catch (RecognitionException e)
			{

				this.lasetRe = e;
				this.isParseSuccess = false;
				return;
			}

			tree = (BeeCommonNodeTree) r.getTree();
			this.reader.close();
			tf.clear();

		}

		// System.out.println(tree.toStringTree());
		// System.out.println(tf.getTextMap());
	}

	protected void getTextByByteWriter(ByteWriter pw) throws IOException, BeeException
	{

		// 解析
		parse();
		if (this.isParseSuccess)
		{

			try
			{
				context.set("__pw", pw);
				context.set("__group", group);
				context.set("__this", this);
				this.print(tree, this.context, pw);
			}
			catch (BeeRuntimeException ex)
			{
				BeeException beeException = new BeeException(ex);
				beeException.setResource(this.resource);
				if (group.getErrorHandler() != null)
				{
					group.getErrorHandler().processExcption(beeException);
					return;
				}
				else
				{
					throw beeException;
				}

			}
			catch (Exception ex)
			{
				// todo,如何处理，不应该发生
				ex.printStackTrace();

			}
			pw.flush();
		}
		else
		{
			BeeException ex = new BeeException(this.lasetRe);
			ex.setResource(this.resource);
			if (group.getErrorHandler() != null)
			{
				group.getErrorHandler().processExcption(ex);
				return;
			}
			else
			{
				throw ex;
			}

		}
	}

	public void getText(Writer pw) throws IOException, BeeException
	{
		if (directByteOutput && !(pw instanceof ByteWriter))
		{

			throw new IllegalStateException("二进制输出，因此输出流不能是Writer");
		}
		if (pw instanceof ByteWriter)
		{
			this.getTextByByteWriter((ByteWriter) pw);
		}
		else
		{
			this.getTextByByteWriter(new ByteWriter(pw));
		}

	}

	private void parserHolder(BeeCommonNodeTree t, Context localCtx, ByteWriter pw) throws IOException
	{

		List list = t.getChildren();
		BeeCommonNodeTree vartextNode = (BeeCommonNodeTree) list.get(0);
		Object value = expr.eval((BeeCommonNodeTree) vartextNode.getChild(0), localCtx);
		if (vartextNode.getChildren().size() > 1)
		{

			// miss value
			if (expr.continueFormat)
			{
				List fmList = vartextNode.getChildren().subList(1, vartextNode.getChildCount());
				for (int i = 0; i < fmList.size(); i++)
				{
					BeeCommonNodeTree fmNode = (BeeCommonNodeTree) fmList.get(i);
					BeeCommonNodeTree fmNameNode = (BeeCommonNodeTree) fmNode.getChild(0);
					String fmFunction = (String) fmNameNode.getCached();
					if (fmFunction == null)
					{
						fmFunction = BeetlUtil.getFunctionFullName(fmNameNode);
						fmNameNode.setCached(fmFunction);
					}
					String pattern = null;
					if (fmNode.getChildCount() == 2)
					{
						pattern = ((BeeCommonNodeTree) fmNode.getChild(1)).getToken().getText();
						pattern = pattern.substring(1, pattern.length() - 1);
					}
					Format format = group.getFormat(fmFunction);
					if (format == null)
					{
						throw new BeeRuntimeException(BeeRuntimeException.FORMAT_NOT_FOUND,
								((BeeCommonNodeTree) fmNode.getChild(0)).getToken());
					}
					try
					{
						value = format.format(value, pattern);
					}
					catch (Exception ex)
					{
						throw new BeeRuntimeException(BeeRuntimeException.NATIVE_CALL_EXCEPTION,
								((BeeCommonNodeTree) fmNode.getChild(0)).getToken(), ex);
					}

				}
			}
			else
			{
				expr.continueFormat = true;
			}

		}
		if (value != null)
		{
			if (directByteOutput && value instanceof byte[])
			{
				pw.write((byte[]) value);
			}
			else
			{
				pw.write(value.toString());
			}

		}

	}

	public void print(BeeCommonNodeTree t, Context localCtx, ByteWriter pw) throws IOException
	{

		if (this.jump > this.FOR_RESET)
		{
			return;
		}
		if (t.getToken() == null)
		{
			for (int i = 0; i < t.getChildCount(); i++)
			{

				print((BeeCommonNodeTree) t.getChild(i), localCtx, pw);

				if (this.jump > this.FOR_RESET)
				{

					return;
				}
			}
		}
		else
		{
			int tokeType = t.getToken().getType();
			switch (tokeType)
			{
				case BeeParser.HOLDER:
				{

					if (t.getChildCount() == 2)
					{
						// SAFE OUTPUT
						try
						{
							parserHolder(t, localCtx, pw);
						}
						catch (Exception ex)
						{
							// do nothing for save output
						}

					}
					else
					{
						parserHolder(t, localCtx, pw);
					}

					break;
				}
				case BeeParser.TEXT_HOLDER:
				{

					Object value = t.getCached();
					if (value == null)
					{
						String id = t.getChild(0).getText();
						value = this.context.getTextVar(id);
						t.setCached(value);
					}

					if (directByteOutput)
					{

						byte[] bs = (byte[]) value;
						pw.write(bs);

					}
					else
					{

						pw.write((String) value);

					}

					break;
				}
				case BeeParser.ASSIGNMENT:
				{
					List list = t.getChildren();
					BeeCommonNodeTree varNode = (BeeCommonNodeTree) list.get(0);
					String varName = varNode.getText();
					if (list.size() == 2)
					{
						BeeCommonNodeTree expNode = (BeeCommonNodeTree) list.get(1);
						Object value = expr.eval(expNode, localCtx);
						BeeCommonNodeTree parentTree = (BeeCommonNodeTree) t.getParent();
						if (parentTree != null && !parentTree.isNil()
								&& parentTree.getToken().getType() == BeeParser.VAR_DEFINE)
						{
							if (localCtx.contain(varName))
							{
								throw new BeeRuntimeException(BeeRuntimeException.VAR_ALREADY_DEFINED,
										varNode.getToken());
							}
							else
							{
								localCtx.defineVar(varName, value, varNode.getToken());
							}

						}
						else
						{
							try
							{
								localCtx.setVar(varName, value);
							}
							catch (Exception ex)
							{
								throw new BeeRuntimeException(BeeRuntimeException.VAR_READONLY, varNode.getToken(), ex);
							}

						}

					}
					break;
				}
				case BeeParser.IF:
				{
					List list = t.getChildren();
					BeeCommonNodeTree conditionNode = (BeeCommonNodeTree) list.get(0);
					boolean st1 = expr.condition(conditionNode, localCtx);
					if (st1)
					{
						BeeCommonNodeTree block = (BeeCommonNodeTree) list.get(1);
						print(block, localCtx, pw);
					}
					else
					{
						if (list.size() == 3)
						{
							BeeCommonNodeTree block = (BeeCommonNodeTree) list.get(2);
							print(block, localCtx, pw);
						}
					}
					break;
				}
				case BeeParser.SLIST:
				{

					localCtx = new Context(localCtx);
					List list = t.getChildren();
					if (list == null || list.size() == 0)
						return;
					int size = list.size();
					BeeCommonNodeTree node = null;
					for (int i = 0; i < size; i++)
					{
						node = (BeeCommonNodeTree) list.get(i);
						print(node, localCtx, pw);
						if (this.jump == this.FOR_CONTINUE || this.jump == this.FOR_BREAK || this.jump == this.RETURN)
						{
							return;
						}
					}
					break;
				}
				case BeeParser.VAR_DEFINE:
				{
					BeeCommonNodeTree assignNode = (BeeCommonNodeTree) t.getChild(0);
					print(assignNode, localCtx, pw);
					break;
				}
				case BeeParser.FUNCTION:
				{
					this.expr.function(t, localCtx);
					break;
				}
				case BeeParser.CLASS_FUNCTION:
				{
					this.expr.classNativeCall(t, localCtx);
					break;
				}
				case BeeParser.TEXT_PROCESS:
				{
					BeeCommonNodeTree processNode = (BeeCommonNodeTree) t.getChild(0);
					String processName = (String) processNode.getCached();
					if (processName == null)
					{
						processName = BeetlUtil.getFunctionFullName(processNode);
						processNode.setCached(processName);
					}
					Tag pf = null;
					pf = this.getTag(processName);
					if (pf == null)
					{
						throw new BeeRuntimeException(BeeRuntimeException.TAG_NOT_FOUND, processNode.getToken());
					}
					pf.setContext(localCtx.getNewContext());

					BeeCommonNodeTree blockNode = (BeeCommonNodeTree) t.getChild(1);
					int size = t.getChildCount();
					Object[] paras = new Object[size - 2];
					BeeCommonNodeTree paraNode = null;
					for (int i = 2; i < size; i++)
					{
						paraNode = (BeeCommonNodeTree) t.getChild(i);
						paras[i - 2] = expr.eval(paraNode, localCtx);

					}

					try
					{
						pf.setParas(paras);
						boolean usingByte = false;
						if (group.directByteOutput && pf instanceof ByteSupportTag)
						{
							usingByte = true;
						}

						if (pf.requriedInput())
						{

							ByteWriter temp = pw.getTempWriter();
							ByteWriter oldWriter = (ByteWriter) localCtx.getVar("__pw");
							localCtx.set("__pw", temp);
							this.print(blockNode, localCtx, temp);

							if (usingByte)
							{

								byte[] input = temp.getTempByte();
								localCtx.set("__pw", oldWriter);
								((ByteSupportTag) pf).setByteInput(input);
							}
							else
							{

								String input = temp.toString();
								localCtx.set("__pw", oldWriter);
								pf.setInput(input);
							}

						}

						if (usingByte)
						{
							byte[] output = null;
							output = ((ByteSupportTag) pf).getOutputAsByte();
							if (output != null)
							{
								pw.write(output);
							}

						}
						else
						{
							String output = pf.getOutput();
							if (output != null)
							{
								pw.write(output);
							}

						}

					}
					catch (BeeRuntimeException bre)
					{
						throw bre;
					}
					catch (Exception ex)
					{
						throw new BeeRuntimeException(BeeRuntimeException.NATIVE_CALL_EXCEPTION,
								processNode.getToken(), ex);

					}

					break;
				}
				case BeeParser.FOR:
				{
					localCtx = new Context(localCtx);
					BeeCommonNodeTree idNode = (BeeCommonNodeTree) t.getChild(0);
					BeeCommonNodeTree varRef = (BeeCommonNodeTree) t.getChild(1);
					Object o = this.expr.eval(varRef, localCtx);
					if (o == null && t.getChildCount() != 4)
					{
						throw new BeeRuntimeException(BeeRuntimeException.NULL, varRef.getToken());
					}
					else if (o == null && t.getChildCount() == 4)
					{
						// safe output ,just renturn
						return;
					}
					String name = idNode.getToken().getText();

					localCtx.defineVar(name + "_index", 0, idNode.getToken());
					int index = 0;
					if (o instanceof Collection)
					{
						Collection c = (Collection) o;
						Iterator it = c.iterator();

						localCtx.defineVar(name + "_size", c.size());
						while (it.hasNext())
						{
							Object temp = it.next();
							if (localCtx.contain(name))
							{
								localCtx.setVar(name, temp);

							}
							else
							{
								localCtx.defineVar(name, temp);
							}
							localCtx.setVar(name + "_index", index++);

							BeeCommonNodeTree slist = (BeeCommonNodeTree) t.getChild(2);

							this.print(slist, localCtx, pw);
							if (this.jump == this.FOR_BREAK)
							{
								jump = this.FOR_RESET;
								return;
							}
							else if (jump == this.FOR_CONTINUE)
							{
								jump = this.FOR_RESET;
								continue;
							}
							else if (jump == this.RETURN)
							{
								// beetl return ;
								return;
							}

						}

						jump = this.FOR_RESET;

					}
					else if (o instanceof Map)
					{
						Map map = (Map) o;
						Iterator<Entry> it = map.entrySet().iterator();
						Entry entry0 = null;
						Object key = null;
						Object value = null;
						MapEntry entry = null;
						localCtx.defineVar(name + "_size", map.size());
						while (it.hasNext())
						{
							entry0 = it.next();
							entry = new MapEntry(entry0.getKey(), entry0.getValue());
							if (localCtx.contain(name))
							{
								localCtx.setVar(name, entry);

							}
							else
							{
								localCtx.defineVar(name, entry);
							}
							localCtx.setVar(name + "_index", index++);

							BeeCommonNodeTree slist = (BeeCommonNodeTree) t.getChild(2);
							this.print(slist, localCtx, pw);
							if (this.jump == this.FOR_BREAK)
							{
								jump = this.FOR_RESET;
								return;
							}
							else if (jump == this.FOR_CONTINUE)
							{
								jump = this.FOR_RESET;
								continue;
							}
							else if (jump == this.RETURN)
							{
								// beetl return ;
								return;
							}

						}

					}
					else if (o.getClass().isArray())
					{
						Object[] array = (Object[]) o;
						localCtx.defineVar(name + "_size", array.length);
						for (Object temp : array)
						{

							if (localCtx.contain(name))
							{
								localCtx.setVar(name, temp);

							}
							else
							{
								localCtx.defineVar(name, temp);
							}
							localCtx.setVar(name + "_index", index++);
							BeeCommonNodeTree slist = (BeeCommonNodeTree) t.getChild(2);
							this.print(slist, localCtx, pw);
							if (this.jump == this.FOR_BREAK)
							{
								jump = this.FOR_RESET;
								return;
							}
							else if (jump == this.FOR_CONTINUE)
							{
								jump = this.FOR_RESET;
								continue;
							}
							else if (jump == this.RETURN)
							{
								// beetl return ;
								return;
							}

						}

						jump = this.FOR_RESET;
					}
					else
					{
						throw new BeeRuntimeException(BeeRuntimeException.DO_NOT_SUPPORT, varRef.getToken());
					}
					break;
				}
				case BeeParser.SWITCH:
				{
					BeeCommonNodeTree exp = (BeeCommonNodeTree) t.getChild(0);
					Object o = this.expr.eval(exp, localCtx);
					boolean hasDefault = false;
					boolean isMatch = false;
					int caseCount = t.getChildCount() - 1;
					if (t.getChild(t.getChildCount() - 1).getType() == BeeParser.DEFAULT)
					{
						caseCount = caseCount - 1;
						hasDefault = true;
					}
					BeeCommonNodeTree caseTree = null;
					BeeCommonNodeTree comparedTree = null;
					Object comparedObject = null;
					BeeCommonNodeTree caseExpTree = null;
					for (int i = 0; i < caseCount; i++)
					{
						caseTree = (BeeCommonNodeTree) t.getChild(i + 1);
						comparedTree = (BeeCommonNodeTree) caseTree.getChild(0);
						Object caseObject = this.expr.eval(comparedTree, localCtx);
						if (o.equals(caseObject))
						{
							isMatch = true;
							for (int j = 1; j < caseTree.getChildCount(); j++)
							{
								caseExpTree = (BeeCommonNodeTree) caseTree.getChild(j);
								this.print(caseExpTree, localCtx, pw);
								// 还需要在antlr 语法里判断是否运行continue，这些延迟到以后解决以避免复杂化语法文件
								if (this.jump == this.FOR_BREAK)
								{
									jump = this.FOR_RESET;
									return;
								}
								if (jump == this.RETURN)
								{
									// beetl return ;
									return;
								}
							}

						}

					}

					if (!isMatch && hasDefault)
					{
						BeeCommonNodeTree defaultTree = (BeeCommonNodeTree) t.getChild(t.getChildCount() - 1);
						BeeCommonNodeTree expTree = null;
						for (int i = 0; i < defaultTree.getChildCount(); i++)
						{
							expTree = (BeeCommonNodeTree) defaultTree.getChild(i);
							this.print(expTree, localCtx, pw);
						}

					}
					break;

				}
				case BeeParser.BREAK:
				{
					jump = FOR_BREAK;
					return;
				}
				case BeeParser.CONTINUE:
				{
					jump = FOR_CONTINUE;
					return;
				}
				case BeeParser.RETURN:
				{
					jump = RETURN;
					return;
				}
				default:
				{
					for (int i = 0; i < t.getChildCount(); i++)
					{

						print((BeeCommonNodeTree) t.getChild(i), localCtx, pw);

					}
				}
			}

		}

	}

	protected void copyGroupPropertie()
	{
		this.statementEnd = group.getStatementEnd();
		this.statementStart = group.getStatementStart();
		this.placeholderEnd = group.getPlaceholderEnd();
		this.placeholderStart = group.getPlaceholderStart();
		this.isStrict = group.isStrict;
		this.nativeCall = group.nativeCall;
		this.directByteOutput = group.directByteOutput;

	}

	BeeTemplate copy()
	{
		BeeTemplate template = new BeeTemplate();
		template.antlrReader = this.antlrReader;
		template.lexer = this.lexer;
		template.lasetRe = this.lasetRe;
		template.parser = this.parser;
		template.tokens = this.tokens;
		template.tree = this.tree;
		template.tf = this.tf;
		template.group = this.group;
		template.reader = this.reader;
		template.isParsed = true;
		template.isParseSuccess = this.isParseSuccess;
		template.context.text = this.context.text;
		template.resource = this.resource;
		template.directByteOutput = this.directByteOutput;
		return template;

	}

	public Map<String, Object> getVars()
	{
		return this.context.vars;
	}

	public static void main(String[] args) throws Exception
	{

		{
			// DefaultErrorHandler.OFFSET = 10;
			// String input =
			// "<%var c=[a,b,'hello',{'key1':'value1','key2':'value2'}];%>$c[3]['key1']$";
			// BeeTemplate t = new BeeTemplate(input);
			// t.setStatementStart("<%");
			// t.setStatementEnd("%>");
			//
			// TypeTable table = t.getTypeTable();
			// List<String> types = table.allType();
			// System.out.println(types);
			// t.set("a", 3);
			// t.set("b", -1);
			//
			// String actual = t.getTextAsString();
			// System.out.println(actual);

		}

		{
			BeeTemplate t = new BeeTemplate("@{'/'+namespace}");
			t.config("<%", "%>", "@{", "}");
			t.set("namespace", "system");
			String result = t.getTextAsString();
			System.out.println(result);
		}

	}

	@Override
	public String getCR()
	{
		return this.resource.CR;
	}

	@Override
	public Context getContext()
	{
		return this.context;
	}

}
